一文讲透JavaScript闭包与立即执行函数表达式(IIFE) 您所在的位置:网站首页 js 查找函数内部定义的函数是什么 一文讲透JavaScript闭包与立即执行函数表达式(IIFE)

一文讲透JavaScript闭包与立即执行函数表达式(IIFE)

2024-07-12 11:47| 来源: 网络整理| 查看: 265

引言

闭包是一种函数的特性,用于捕获和保存其所在作用域的变量,而IIFE是一种用来创建函数作用域的模式。在JavaScript中,我们可以将闭包和IIFE结合使用,但它们并不是彼此依赖的概念。

虽然我们可以在IIFE中使用闭包,但是闭包并不依赖于IIFE的存在。闭包可以与任何函数一起使用,不管是普通函数还是IIFE。

关于闭包和IIFE,本文将分别讨论它们在JavaScript开发中的应用场景和好处。这样可以更清楚地理解它们的作用和关系,并有效地运用它们来提升代码质量和可维护性。

一、深入闭包的理解1.1、闭包的概念

闭包(closure)是指一个函数可以访问并操作其自身作用域以外的变量。换句话说,闭包是一种能够访问其父函数作用域中的变量的函数。

在JavaScript中,当一个函数内部定义了另一个函数,并且内部的函数引用了外部函数的变量时,就创建了一个闭包。内部函数可以访问外部函数的变量,即使外部函数已经执行完毕,这些变量仍然可以在内部函数中使用。

闭包的一个常见用途是创建私有变量。通过使用闭包,可以在函数内部定义一个变量,使其在外部无法访问。这样可以提供更好的封装和数据隐藏。

以下是一个简单的闭包示例:

代码语言:javascript复制function outerFunction() { var outerVariable = 'Hello'; function innerFunction() { console.log(outerVariable); } return innerFunction; } var closure = outerFunction(); closure(); // 输出:Hello

在上面的例子中,outerFunction内部定义了outerVariable变量和innerFunction函数。innerFunction函数引用了outerVariable变量,并且作为一个闭包被返回出来。当我们调closure时,它保留了对outerVariable的引用,因此可以在执行时访问并打印出Hello。

1.2、闭包的特性

JavaScript之所以有闭包,是因为它采用了词法作用域的函数定义方式。

闭包的存在有以下几个重要原因:

保护变量:闭包可以创建私有变量,通过将变量封装在函数内部,外部无法直接访问,从而实现信息隐藏和保护变量的安全性实现数据封装:闭包提供了一种封装数据的方式,在函数内部定义的变量只能在函数内部访问,外部无法修改或者获取,从而实现了数据私有化。延长变量的生命周期:当函数执行完毕后,其作用域中的变量通常会被销毁,但是闭包可以延长变量的生命周期。内部函数仍然可以引用外部函数中的量,因此这些变量不会被垃圾回收机制销毁,可以在内部函数中继续使用。创建回调和异步操作:闭包可以用于创建回调函数,通过将函数作为参数传递给其他函数,实现函数的延迟执行。

总的来说,闭包在JavaScript中具有重要的作用,可以提供更强大的编程能力,实现数据封装、变量保护、延长变量生命周期等功能。

构建函数工厂

比如有这么一个场景,如何去写一个sum(1)(2) = 3的函数?

分析一下,(sum(1))(2)显然第一个括号执行之后仍然应该是个函数,然后再把第二个参数2传进去。

代码语言:javascript复制function sum(x){ return function(y){ return x+y; } } sum(1)(2); // 输出结果为 3 // 问题,如果第一个数我们需要确定呢? var add1 = sum(1); var add2 = sum(2); add1(5); // 输出结果为 6 add2(6); // 输出结果为 8

我们可以将sum看作是一个函数工厂,你可以用这个工厂创建出你需要的各种函数。

构建私有变量

由于ES6之前的JavaScript是没有类的概念,我们用函数来模拟类。会一点OOP的应该都知道,有些类中的变量我们需要保护不被外界访问到,就有了私有变量的概念。

有种简单的创建类的方式如下

代码语言:txt复制function Person() { this.name = 'anyup1'; this.getName = function(){ return this.name; } } var person = new Person(); person.getName(); // 'anyup1' person.name = 'anyup2'; person.getName(); // 'anyup2'

首先定义了一个Person的类构造器,实例化出一个person对象。但是可以直接被修改内部的变量name,使得人的名字被修改了。我们当然不希望我们的名字被修改。

此处我们换种方式,将name设置为私有变量

代码语言:txt复制 function Person() { var name = 'anyup'; this.getName = function(){ return name; } } var me = new Person(); person.getName(); // 'anyup' person.name = 'anyup2'; // 你仍然可以设置person.name属性,但是这个对象内部的name值是保持不变的。 person.getName(); // 'anyup'

分析一下,为什么说上述的是闭包呢?首先getName函数是包含在Person函数里面,但是看起来好像没有返回。我们来看下me = new Person()做了什么,它其实是创建了一个对象,并且返回。也就是说getName是在此时返回的。然后me.getName()就能使用了。

变量不被回收

由于JavaScript的垃圾回收机制,普通函数执行完之后,变量就会被直接回收。但是,闭包的方式可以让变量一直存在,不被回收。我们来看一个简单的计数器例子。

代码语言:javascript复制 function Counter(){ var count = 0; return function(){ return count++; } } var counter = Counter(); counter(); // 输出 0 counter(); // 输出 1

正是由于这种变量不被回收的机制,这样我们就能实现每次执行counter()的时候count就会在原来的基础上增加1。

1.3、闭包的副作用

由于JavaScript闭包是指函数能够访问其外部函数范围内定义的变量,即使外部函数已经执行完毕。尽管闭包在某些情况下非常有用,但它也可能带来一些副作用。

以下是一些JavaScript闭包可能引发的副作用:

内存泄漏:由于闭包保持对外部变量的引用,这些变量可能会一直存在于内存中,即使它们已经不再需要。如果闭包过多或闭包引用的数据过大,可能会导致内泄漏,影响程序性能。变量生命周期延长:使用闭包可以使变量的生命周期超过它们通常在函数执行结束后被销毁的范围。这可能导致变量长时间占用内存空间,增加内存使用量。性能损失:闭包需要维护对外部变量的引用,当闭包被频繁调用时,会增加额外的性能开销。这是因为每个闭包都需要在内存中保存对外部变量的引用,而且包访问外部变量的速度相对较慢。

出于以上原因,在编写代码时,应该谨慎使用闭包。确保确实需要使用闭包,并注意处理闭包带来的副作用。对于不再使用的闭包,及时释放相关资源,以避免内存泄漏。同时,尽量简化闭包的使用场景,以提高代码可读性和维护性。

以下是一个简单的示例,说明闭包内存泄漏的风险:

代码语言:javascript复制function Person(name ) { this.getName = function(){ return name; } this.sayHi = function(){ alert("say Hi"); } } var me = new Person('me'); var you = new Person('you');

上面的示例代码每创建一个对象,都有创建出一个相同的sayHello方法。这个方法并没有用到私有变量name,其实就根本不需要在Person内部去定义这样的一个闭包。更好的方式是将这个方法添加在Person的原型链上,如下图所示:

优化后的代码如下所示:

代码语言:txt复制 function Person(name ) { this.getName = function(){ return name; } } Person.prototype.sayHi = function(){ alert("say Hi"); }1.4、闭包的经典场景

经典的JavaScript闭包应用场景中,使用闭包在for循环中是一个常见的例子。在循环中使用闭包可以避免变量共享和作用域问题,确保在异步操作中使用正确的值。

考虑以下示例,我们使用for循环创建了多个定时器,每隔一秒输出对应的数字:

代码语言:javascript复制for (var i = 1; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有